

/**
 * 可视域分析。
 *
 * @param {Cesium.Viewer} viewer Cesium三维视窗。
 * @param {Object} options 选项。
 * @param {Cesium.Cartesian3} options.viewPosition 观测点位置。
 * @param {Cesium.Cartesian3} options.viewPositionEnd 最远观测点位置（如果设置了观测距离，这个属性可以不设置）。
 * @param {Number} options.viewDistance 观测距离（单位`米`，默认值100）。
 * @param {Number} options.viewHeading 航向角（单位`度`，默认值0）。
 * @param {Number} options.viewPitch 俯仰角（单位`度`，默认值0）。
 * @param {Number} options.horizontalViewAngle 可视域水平夹角（单位`度`，默认值90）。
 * @param {Number} options.verticalViewAngle 可视域垂直夹角（单位`度`，默认值60）。
 * @param {Cesium.Color} options.visibleAreaColor 可视区域颜色（默认值`绿色`）。
 * @param {Cesium.Color} options.invisibleAreaColor 不可视区域颜色（默认值`红色`）。
 * @param {Boolean} options.enabled 阴影贴图是否可用。
 * @param {Boolean} options.softShadows 是否启用柔和阴影。
 * @param {Boolean} options.size 每个阴影贴图的大小。
 * @param {Camera} options.lightCamera 相机位置。
 * @param {} options.shadowMap 阴影贴图。
 * @param {} options.postStage 后处理程序。
 * @param {} options.frustumOutline 丝锥线
 * @param {} options.sketch 视网
 * @param {} _positions 编辑可视域数组
 */

import ViewShedStageShader from '../shader/ViewShedStageShader.glsl?raw'

export default class ViewShedStage {
  /**
   * 
   * @param {*} viewer 
   * @param {*} options 
   */
  constructor(viewer, options = {}) {
    this._viewer = viewer
    this._positions = []

    this._viewPosition = options.viewPosition
    this.viewPositionEnd = options.viewPositionEnd
    this._viewDistance = this.viewPositionEnd ? Cesium.Cartesian3.distance(this.viewPosition, this.viewPositionEnd) : (options.viewDistance || 100.0)

    this._viewHeading = this.viewPositionEnd ? getHeading(this.viewPosition, this.viewPositionEnd) : (options.viewHeading || 0.0)
    this._viewPitch = this.viewPositionEnd ? getPitch(this.viewPosition, this.viewPositionEnd) : (options.viewPitch || 0.0)
    this._horizontalViewAngle = options.horizontalViewAngle || 50.0
    this._verticalViewAngle = options.verticalViewAngle || 60.0

    this.visibleAreaColor = options.visibleAreaColor || Cesium.Color.fromCssColorString('#32ff7e')
    this.invisibleAreaColor = options.invisibleAreaColor || Cesium.Color.fromCssColorString('#ffa502')
    
    this.enabled = (typeof options.enabled === "boolean") ? options.enabled : true
    this.softShadows = (typeof options.softShadows === "boolean") ? options.softShadows : true
    this.size = options.size || 2048

    this.lightCamera = undefined  //相机位置。
    this.shadowMap = undefined  //阴影贴图。
    this.postStage = undefined  //后处理程序。
    this.frustumOutline = undefined //丝锥线
    this.sketch = undefined //视网

    this.type = 'viewShed'

    this.update()
  }

  /**
   * 创建灯光相机
   */
  createLightCamera() {
    this.lightCamera = new Cesium.Camera(this._viewer.scene)
    this.setCameraView()
  }

  /**
   * 创建阴影贴图
   */
  createShadowMap() {
    this.shadowMap && this.shadowMap.destroy()
    this.shadowMap = new Cesium.ShadowMap({
      context: this._viewer.scene.context,
      lightCamera: this.lightCamera,
      enabled: this.enabled,
      isPointLight: true,
      pointLightRadius: this.viewDistance,
      cascadesEnabled: false,
      size: this.size,
      softShadows: this.softShadows,
      normalOffset: false,
      fromLightSource: false
    })
    this._viewer.scene.shadowMap = this.shadowMap
  }

  /**
   * 设置相机朝向的方向
   */
  setCameraView() { 
    // 设置相机位置
    this.lightCamera.position = this._viewPosition

    // 设置相机最近和最远距离
    this.lightCamera.frustum.near = this._viewDistance * 0.001
    this.lightCamera.frustum.far = this._viewDistance

    // 转换水平夹角和垂直角度 为弧度
    const hr = Cesium.Math.toRadians(this._horizontalViewAngle)
    const vr = Cesium.Math.toRadians(this._verticalViewAngle)
    // 设置相机视角比例
    const aspectRatio = (this.viewDistance * Math.tan(hr / 2) * 2) /
                        (this.viewDistance * Math.tan(vr / 2) * 2)
    // console.log(aspectRatio , hr)
    this.lightCamera.frustum.aspectRatio = aspectRatio

    if (hr > vr) {
      this.lightCamera.frustum.fov = hr
    } else {
      this.lightCamera.frustum.fov = vr
    }

    this.lightCamera.setView({
      destination: this._viewPosition,
      orientation: {
        heading: Cesium.Math.toRadians(this._viewHeading || 0),
        pitch: Cesium.Math.toRadians(this._viewPitch || 0),
        roll: 0
      }
    })
  }

  /**
   * 创建后处理
   */
  createPostStage() {
    this.postStage && this._viewer.scene.postProcessStages.remove(this.postStage)

    const postStage = new Cesium.PostProcessStage({
      fragmentShader: ViewShedStageShader,
      uniforms: {
        shadowMap_textureCube: () => {
          this.shadowMap.update(Reflect.get(this._viewer.scene, "_frameState"))
          return Reflect.get(this.shadowMap, "_shadowMapTexture")
        },
        shadowMap_matrix: () => {
          this.shadowMap.update(Reflect.get(this._viewer.scene, "_frameState"))
          return Reflect.get(this.shadowMap, "_shadowMapMatrix")
        },
        shadowMap_lightPositionEC: () => {
          this.shadowMap.update(Reflect.get(this._viewer.scene, "_frameState"))
          return Reflect.get(this.shadowMap, "_lightPositionEC")
        },
        shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness: () => {
          this.shadowMap.update(Reflect.get(this._viewer.scene, "_frameState"))
          const bias = this.shadowMap._pointBias;
          return Cesium.Cartesian4.fromElements(
            bias.normalOffsetScale,
            this.shadowMap._distance,
            this.shadowMap.maximumDistance,
            0.0,
            new Cesium.Cartesian4()
          );
        },
        shadowMap_texelSizeDepthBiasAndNormalShadingSmooth: () => {
          this.shadowMap.update(Reflect.get(this._viewer.scene, "_frameState"));
          const bias = this.shadowMap._pointBias;
          const scratchTexelStepSize = new Cesium.Cartesian2();
          const texelStepSize = scratchTexelStepSize;
          texelStepSize.x = 1.0 / this.shadowMap._textureSize.x;
          texelStepSize.y = 1.0 / this.shadowMap._textureSize.y;

          return Cesium.Cartesian4.fromElements(
            texelStepSize.x,
            texelStepSize.y,
            bias.depthBias,
            bias.normalShadingSmooth,
            new Cesium.Cartesian4()
          );
        },
        camera_projection_matrix: this.lightCamera.frustum.projectionMatrix,
        camera_view_matrix: this.lightCamera.viewMatrix,
        helsing_viewDistance: () => {
          return this.viewDistance
        },
        helsing_visibleAreaColor: this.visibleAreaColor,
        helsing_invisibleAreaColor: this.invisibleAreaColor,
      }
    })
    this.postStage = this._viewer.scene.postProcessStages.add(postStage)
  }

  /**
   * 创建视锥线
   */
  drawFrustumOutine() {
    this.frustumOutline && this.frustumOutline.destroy()
    const scratchRight = new Cesium.Cartesian3()
    const scratchRotation = new Cesium.Matrix3()
    const scratchOrientation = new Cesium.Quaternion()
    const direction = this.lightCamera.directionWC
    const up = this.lightCamera.upWC

    let right = this.lightCamera.rightWC
    right = Cesium.Cartesian3.negate(right, scratchRight)
    let rotation = scratchRotation
    Cesium.Matrix3.setColumn(rotation, 0, right, rotation)
    Cesium.Matrix3.setColumn(rotation, 1, up, rotation)
    Cesium.Matrix3.setColumn(rotation, 2, direction, rotation)
    let orientation = Cesium.Quaternion.fromRotationMatrix(rotation, scratchOrientation)

    let instance = new Cesium.GeometryInstance({
      geometry: new Cesium.FrustumOutlineGeometry({
        frustum: this.lightCamera.frustum,
        origin: this.viewPosition,
        orientation: orientation
      }),
      id: Math.random().toString(36).substr(2),
      attributes: {
        color: Cesium.ColorGeometryInstanceAttribute.fromColor(
          Cesium.Color.YELLOWGREEN//new Cesium.Color(0.0, 1.0, 0.0, 1.0)
        ),
        show: new Cesium.ShowGeometryInstanceAttribute(true)
      }
    })

    this.frustumOutline = this._viewer.scene.primitives.add(
      new Cesium.Primitive({
        geometryInstances: [instance],
        appearance: new Cesium.PerInstanceColorAppearance({
          flat: true,
          translucent: false
        })
      })
    )
  }

  /**
   * 创建视网
   */
  drawSketch() {
    this.sketch && this._viewer.entities.removeById(this.sketch.id)
    this.sketch = this._viewer.entities.add({
      name: 'sketch',
      position: new Cesium.CallbackProperty((time , result ) => {
        return this.viewPosition
      } , false),
      orientation: new Cesium.CallbackProperty((time , result) => {
        return Cesium.Transforms.headingPitchRollQuaternion(
          this.viewPosition,
          Cesium.HeadingPitchRoll.fromDegrees(this.viewHeading - 90, this.viewPitch, 0.0)
        )
      }, false),
      ellipsoid: {
        radii: new Cesium.CallbackProperty((time , result) => {
          return new Cesium.Cartesian3(
            this.viewDistance,
            this.viewDistance,
            this.viewDistance
          )
        }),
        minimumClock: new Cesium.CallbackProperty((time , result) => {
          return Cesium.Math.toRadians(-this.horizontalViewAngle / 2)
        },false),
        maximumClock:new Cesium.CallbackProperty((time , result) => {
          return Cesium.Math.toRadians(this.horizontalViewAngle / 2)
        },false),
        minimumCone:new Cesium.CallbackProperty((time , result) => {
          return Cesium.Math.toRadians(this.verticalViewAngle + 7.75)
        },false),
        maximumCone:new Cesium.CallbackProperty((time , result) => {
          return Cesium.Math.toRadians(180 - this.verticalViewAngle - 7.75)
        },false),
        fill: false,
        outline: true,
        subdivisions: 256,
        stackPartitions: 64,
        slicePartitions: 64,
        outlineColor: Cesium.Color.YELLOWGREEN
      }
    })
  }

  /**
   * 添加可视域
   */
  add() {
    // 创建灯光相机
    this.createLightCamera()
    // 创建阴影贴图
    this.createShadowMap()
    // 创建后处理程序
    this.createPostStage()
    //创建视锥线
    this.drawFrustumOutine()
    // 创建视网
    this.drawSketch()
  }

  /**
   * 
   */

  update() {
    this.clear()
    this.add()
  }

  /**
   * 
   */
  clear() {
    if (this.sketch) {
      this._viewer.entities.removeById(this.sketch.id)
      this.sketch = null
    }
    if (this.frustumOutline) {
      this.frustumOutline.destroy()
      this.frustumOutline = null
    }
    if (this.postStage) {
      this._viewer.scene.postProcessStages.remove(this.postStage)
      this.postStage = null
    }
  }


  get viewer(){
    return this._viewer
  }

  get viewHeading(){
    return this._viewHeading
  }

  set viewHeading(heading){
    this._viewHeading = heading
    this.setCameraView()
    this.drawFrustumOutine()
  }

  get viewPitch(){
    return this._viewPitch
  }

  set viewPitch(pitch){
    this._viewPitch = pitch
    this.setCameraView()
    this.drawFrustumOutine()
  }

  get horizontalViewAngle(){
    return this._horizontalViewAngle
  }

  set horizontalViewAngle(val){
    this._horizontalViewAngle = val
    this.setCameraView()
    this.drawFrustumOutine()
  }
  
  get verticalViewAngle(){
    return this._verticalViewAngle
  }

  set verticalViewAngle(val){
    this._verticalViewAngle = val
    this.setCameraView()
    this.drawFrustumOutine()
  }

  get viewPosition(){
    return this._viewPosition
  }

  set viewPosition(val){
    this._viewPosition = val
    this.setCameraView()
    this.createShadowMap()
    this.drawFrustumOutine()
  }

  get viewDistance(){
    return this._viewDistance
  }

  set viewDistance(val){
    this._viewDistance = val
    this.setCameraView()
    this.createShadowMap()
    this.createPostStage()
    this.drawFrustumOutine()
  }

}

/**
 * 获取heading角
 * @param {*} fromPosition 
 * @param {*} toPosition 
 * @returns 
 */
function getHeading(fromPosition, toPosition) {
  let finalPosition = new Cesium.Cartesian3()
  let matrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(fromPosition)
  Cesium.Matrix4.inverse(matrix4, matrix4)
  Cesium.Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition)
  Cesium.Cartesian3.normalize(finalPosition, finalPosition)
  return Cesium.Math.toDegrees(Math.atan2(finalPosition.x, finalPosition.y))
}

/**
 * 获取俯仰角
 * @param {*} fromPosition 
 * @param {*} toPosition 
 * @returns 
 */
function getPitch(fromPosition, toPosition) {
  let finalPosition = new Cesium.Cartesian3()
  let matrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(fromPosition)
  Cesium.Matrix4.inverse(matrix4, matrix4)
  Cesium.Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition)
  Cesium.Cartesian3.normalize(finalPosition, finalPosition)
  return Cesium.Math.toDegrees(Math.asin(finalPosition.z))
}